package com.kk.xhr.model.service.impl;

import com.kk.xhr.common.pojo.ApplyStatusEnum;
import com.kk.xhr.common.pojo.BinaryEnum;
import com.kk.xhr.model.assembler.ComplementSignStructKit;
import com.kk.xhr.model.assembler.SignStructKit;
import com.kk.xhr.model.dao.ComplementSignDynamicSqlSupport;
import com.kk.xhr.model.dao.ComplementSignMapper;
import com.kk.xhr.model.dao.SignDynamicSqlSupport;
import com.kk.xhr.model.dao.SignMapper;
import com.kk.xhr.model.dao.custom.SignCustomMapper;
import com.kk.xhr.model.dto.ApplyQueryRequestDTO;
import com.kk.xhr.model.dto.ComplementSignDTO;
import com.kk.xhr.model.dto.PageDto;
import com.kk.xhr.model.dto.SignDTO;
import com.kk.xhr.model.dto.SignQueryRequestDTO;
import com.kk.xhr.model.entity.ComplementSign;
import com.kk.xhr.model.entity.Sign;
import com.kk.xhr.model.service.ISignService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.tuple.Pair;
import org.mybatis.dynamic.sql.render.RenderingStrategies;
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
import org.mybatis.dynamic.sql.where.AbstractWhereDSL;
import org.springframework.stereotype.Repository;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import static org.mybatis.dynamic.sql.SqlBuilder.isEqualTo;
import static org.mybatis.dynamic.sql.SqlBuilder.isEqualToWhenPresent;
import static org.mybatis.dynamic.sql.SqlBuilder.isGreaterThanOrEqualTo;
import static org.mybatis.dynamic.sql.SqlBuilder.isGreaterThanOrEqualToWhenPresent;
import static org.mybatis.dynamic.sql.SqlBuilder.isLessThanOrEqualTo;
import static org.mybatis.dynamic.sql.SqlBuilder.isLessThanOrEqualToWhenPresent;
import static org.mybatis.dynamic.sql.SqlBuilder.or;
import static org.mybatis.dynamic.sql.SqlBuilder.select;

/**
 * 签到服务接口实现
 *
 * @author luokexiong
 * @version 1.0 2021/2/19
 * @since 1.0.0
 */
@Repository
@RequiredArgsConstructor
public class SignServiceImpl implements ISignService {
    private final SignMapper signMapper;
    private final SignCustomMapper signCustomMapper;
    private final ComplementSignMapper complementSignMapper;
    private final SignStructKit instance = SignStructKit.INSTANCE;
    private final ComplementSignStructKit complementSignStructKit = ComplementSignStructKit.INSTANCE;

    /**
     * {@inheritDoc}
     */
    @Override
    public PageDto<SignDTO> query(SignQueryRequestDTO dto) {
        long count = signMapper.count(dsl -> applyQueryArgs(dsl.where(), dto));
        if (0 == count) {
            return PageDto.empty();
        }
        return new PageDto<>(count, signMapper.select(dsl -> applyQueryArgs(dsl.where(), dto)
            .orderBy(SignDynamicSqlSupport.signDate.descending())
            .limit(dto.getLimit())
            .offset(dto.getOffset()))
            .stream()
            .map(instance::toDto)
            .collect(Collectors.toList()));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PageDto<ComplementSignDTO> query(Long userId, ApplyQueryRequestDTO dto) {
        long count = complementSignMapper.count(dsl -> applyQueryArgs(dsl.where(), dto));
        if (0 == count) {
            return PageDto.empty();
        }
        return new PageDto<>(count, complementSignMapper.select(dsl ->
            applyQueryArgs(dsl.where(), dto)
                .orderBy(ComplementSignDynamicSqlSupport.id.descending())
                .limit(dto.getLimit()).offset(dto.getOffset()))
            .stream()
            .map(complementSignStructKit::toDto)
            .collect(Collectors.toList()));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Optional<SignDTO> query(Long userId, LocalDate begin) {
        LocalDateTime dayStart = LocalDateTime.of(begin, LocalTime.MIN);
        return signMapper.selectOne(dsl -> dsl
            .where(SignDynamicSqlSupport.userId, isEqualTo(userId))
            .and(SignDynamicSqlSupport.signDate, isEqualTo(dayStart))
            .and(SignDynamicSqlSupport.status, isEqualTo(BinaryEnum.TRUE.getValue()))
            .orderBy(SignDynamicSqlSupport.id.descending())
            .limit(1))
            .map(instance::toDto);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Optional<SignDTO> queryById(Long id) {
        return signMapper.selectByPrimaryKey(id).map(instance::toDto);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Optional<ComplementSignDTO> queryComplementById(Long id) {
        return complementSignMapper.selectOne(dsl -> dsl
            .where(ComplementSignDynamicSqlSupport.id, isEqualTo(id))
            .and(ComplementSignDynamicSqlSupport.status, isEqualTo(ApplyStatusEnum.APPLYING.getValue())))
            .map(complementSignStructKit::toDto);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Long> queryWeekSign(LocalDateTime mon, LocalDateTime sun) {
        return signCustomMapper.queryWeekRecord(mon, sun);
    }

    /**
     * 查询当天签到的用户id
     *
     * @param now 当前时间
     * @return 签到用户id
     */
    @Override
    public Set<Long> queryDaySigned(LocalDate now) {
        SelectStatementProvider provider = select(SignDynamicSqlSupport.userId)
            .from(SignDynamicSqlSupport.sign)
            .where(SignDynamicSqlSupport.signDate, isEqualTo(LocalDateTime.of(now, LocalTime.MIN)))
            .and(SignDynamicSqlSupport.status, isEqualTo(BinaryEnum.TRUE.getValue()))
            .build().render(RenderingStrategies.MYBATIS3);
        return signMapper.selectMany(provider)
            .stream()
            .map(Sign::getUserId)
            .collect(Collectors.toSet());
    }

    /**
     * 查询上个月所有考勤记录并计算时长
     *
     * @param userId 用户id
     * @param mouth  上个月第一天和最后一天
     * @return 上个月出勤总时长(小时)和缺失时间(分钟)
     */
    @Override
    public Pair<Long, Long> queryMouthSign(Long userId, Pair<LocalDateTime, LocalDateTime> mouth) {
        SelectStatementProvider provider = select(SignDynamicSqlSupport.duration, SignDynamicSqlSupport.lack)
            .from(SignDynamicSqlSupport.sign)
            .where(SignDynamicSqlSupport.userId, isEqualTo(userId))
            .and(SignDynamicSqlSupport.signDate, isGreaterThanOrEqualTo(mouth.getLeft()))
            .and(SignDynamicSqlSupport.signDate, isLessThanOrEqualTo(mouth.getRight()))
            .and(SignDynamicSqlSupport.status, isEqualTo(BinaryEnum.TRUE.getValue()))
            .build().render(RenderingStrategies.MYBATIS3);
        List<Sign> signList = signMapper.selectMany(provider);
        long workTime = signList.stream().map(Sign::getDuration).count();
        long lackTime = signList.stream().map(Sign::getLack).count() / 60;
        return Pair.of(workTime, lackTime);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean add(SignDTO dto) {
        return 1 == signMapper.insertSelective(instance.toEntity(dto));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean addComplement(ComplementSignDTO dto) {
        return 1 == complementSignMapper.insertSelective(complementSignStructKit.toEntity(dto));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean modify(SignDTO dto) {
        return 1 == signMapper.updateByPrimaryKeySelective(instance.toEntity(dto));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean modifyComplementStatus(Long id, ApplyStatusEnum status) {
        if (null == status) {
            return false;
        }
        return 1 == complementSignMapper.updateByPrimaryKeySelective(new ComplementSign()
            .withId(id)
            .withStatus(status.getValue()));
    }

    /**
     * 应用搜索条件到where中
     *
     * @param where where
     * @param dto   查询dto
     * @param <T>   类型
     * @return 集合搜索条件的where
     */
    private <T extends AbstractWhereDSL<T>> T applyQueryArgs(T where, SignQueryRequestDTO dto) {
        where.and(SignDynamicSqlSupport.userId, isEqualToWhenPresent(dto.getUserId()))
            .and(SignDynamicSqlSupport.signDate, isGreaterThanOrEqualToWhenPresent(dto.getBegin()))
            .and(SignDynamicSqlSupport.signDate, isLessThanOrEqualToWhenPresent(dto.getEnd()))
            .and(SignDynamicSqlSupport.status, isEqualTo(BinaryEnum.TRUE.getValue()));
        if (null != dto.getStatus()) {
            where.and(SignDynamicSqlSupport.beginStatus, isEqualTo(dto.getStatus().getValue()),
                or(SignDynamicSqlSupport.endStatus, isEqualTo(dto.getStatus().getValue())));
        }
        return where;
    }

    /**
     * 应用搜索条件到where中
     *
     * @param where where
     * @param dto   查询dto
     * @param <T>   类型
     * @return 集合搜索条件的where
     */
    private <T extends AbstractWhereDSL<T>> T applyQueryArgs(T where, ApplyQueryRequestDTO dto) {
        where.and(ComplementSignDynamicSqlSupport.userId, isEqualToWhenPresent(dto.getUserId()))
            .and(ComplementSignDynamicSqlSupport.reviewerId, isEqualToWhenPresent(dto.getReviewerId()))
            .and(ComplementSignDynamicSqlSupport.created, isGreaterThanOrEqualToWhenPresent(dto.getBegin()))
            .and(ComplementSignDynamicSqlSupport.created, isLessThanOrEqualToWhenPresent(dto.getEnd()));
        if (null != dto.getStatus()) {
            where.and(ComplementSignDynamicSqlSupport.status, isEqualTo(dto.getStatus().getValue()));
        }
        return where;
    }
}
